(define-library (oaxaca wfs)

  (export wfs-initialized?
          wfs-command
          wfs-idle?
          wfs-wait
          wfs-idle!
          wfs-detector-configuration
          wfs-configure!
          wfs-set-window-center!
          wfs-window
          wfs-pixel-frequency
          wfs-min-frame-frequency
          wfs-max-frame-frequency
          wfs-frame-frequency
          wfs-actual-frame-frequency
          wfs-frame-time
          wfs-frame
          wfs-frames-per-integration
          wfs-integration-time
          wfs-integration
          wfs-integrations-per-exposure
          wfs-exposure-time
          wfs-z-min
          wfs-z-max
          wfs-z-mean
          wfs-fwhm
          wfs-eshm
          wfs-spatial-sigma
          wfs-temporal-sigma
          wfs-search-radius
          wfs-set-search-radius!
          wfs-refinement-radius
          wfs-set-refinement-radius!
          wfs-s-vector
          wfs-r-vector
          wfs-set-r-vector!
          wfs-ds-mean-vector
          wfs-ds-sigma-vector
          wfs-dt-sigma
          wfs-i-matrix
          wfs-set-i-matrix!
          wfs-c-matrix
          wfs-set-c-matrix!
          wfs-g-vector
          wfs-set-g-vector!
          wfs-expose!
          wfs-dark!
          wfs-darks!
          wfs-correct!
          wfs-find-spots!
          wfs-refine-spots!
          wfs-determine-i-matrix!)

  (import (scheme base)
          (scheme char)
          (scheme time)
          (scheme write))

  (import (oaxaca ao)
          (prefix (oaxaca detector) detector:)
          (oaxaca file)
          (prefix (oaxaca foreign) foreign:)
          (oaxaca log)
          (oaxaca thread)
          (oaxaca wfc))

  (begin

    (define wfs-directory-path (make-path (oaxaca-directory-path) "var" "wfs"))
    (make-directory wfs-directory-path)

    (define detector-index (foreign:wfs-detector-index))

    (define (exposure-fits-file-path)
      (make-path wfs-directory-path
                 (string-append "exposure.fits")))

    (define (wfs-initialized?)
      (not (zero? (foreign:wfs-initialized))))

    (define (check-wfs-initialized)
      (unless (wfs-initialized?)
        (error "the wfs is not initialized."))
      (values))

    (define (wfs-idle?)
      (eqv? #\I (foreign:wfs-command)))

    (define (check-wfs-idle)
      (unless (wfs-idle?)
        (error "the wfs is not idle."))
      (values))

    (define (wfs-detector-configuration)
      (apply values
             configuration-name
             (call-with-values (lambda ()
                                 (detector:configuration detector-index))
                               list)))

    (define configuration-name #f)

    (define configuration-alist
      '((full-frame-1     1280 1024  2e6    1)
        (full-frame-2     1280 1024  8e6    2)
        (full-frame-4     1280 1024  8e6    4)
        (full-frame-8     1280 1024 24e6    8)
        (full-frame-16    1280 1024 48e6   16)
        (half-frame-1      640  512  2e6    1)
        (half-frame-2      640  512  2e6    2)
        (half-frame-4      640  512  8e6    4)
        (half-frame-8      640  512  8e6    8)
        (half-frame-16     640  512 24e6   16)
        (half-frame-32     640  512 24e6   32)
        (half-frame-64     640  512 48e6   64)
        (sub-frame-2       240  120  2e6    2)
        (sub-frame-4       240  120  2e6    4)
        (sub-frame-8       240  120  2e6    8)
        (sub-frame-16      240  120  2e6   16)
        (sub-frame-32      240  120  8e6   32)
        (sub-frame-40      240  120  8e6   40)
        (sub-frame-50      240  120  8e6   50)
        (sub-frame-64      240  120  8e6   64)
        (sub-frame-80      240  120  8e6   80)
        (sub-frame-100     240  120 24e6  100)
        (sub-frame-125     240  120 24e6  125)
        (sub-frame-160     240  120 24e6  160)
        (sub-frame-200     240  120 24e6  200)
        (sub-frame-250     240  120 24e6  250)
        (sub-frame-320     240  120 48e6  320)
        (sub-frame-400     240  120 48e6  400)
        (sub-frame-500     240  120 48e6  500)
        (single-spot-1500  120   16 60e6 1500)))

    (define (wfs-set-window-center! cx cy)
      (detector:set-window-center! detector-index cx cy))
      
    (define (wfs-configure! name)      
      (check-wfs-initialized)
      (wfs-idle!)
      (apply detector:set-configuration!
             detector-index
             wfs-directory-path
             name
             (let ((p (assoc name configuration-alist)))
               (unless p
                 (error "invalid configuration name."))
               (cdr p)))
      (set! configuration-name name)
      (wfs-command! #\c #\I)
      (wfs-wait)
      (values))
        
    (define (wfs-window)
      (detector:window detector-index))

    (define (wfs-pixel-frequency)
      (detector:pixel-frequency detector-index))

    (define (wfs-min-frame-frequency)
      (detector:min-frame-frequency detector-index))

    (define (wfs-max-frame-frequency)
      (detector:max-frame-frequency detector-index))

    (define (wfs-frame-frequency)
      (detector:frame-frequency detector-index))

    (define (wfs-actual-frame-frequency)
      (detector:actual-frame-frequency detector-index))

    (define (wfs-frame-time)
      (detector:frame-time detector-index))

    (define (wfs-exposure-time)
      (detector:exposure-time detector-index))

    (define (wfs-integration-time)
      (detector:integration-time detector-index))

    (define (wfs-integrations-per-exposure)
      (detector:integrations-per-exposure detector-index))

    (define (wfs-frames-per-integration)
      (detector:frames-per-integration detector-index))

    (define (wfs-integration)
      (detector:integration detector-index))

    (define (wfs-frame)
      (detector:frame detector-index))

    (define (wfs-z-min)
      (detector:z-min detector-index))

    (define (wfs-z-max)
      (detector:z-max detector-index))

    (define (wfs-z-mean)
      (detector:z-mean detector-index))

    (define (wfs-fwhm)
      (detector:fwhm detector-index))

    (define (wfs-eshm)
      (detector:eshm detector-index))

    (define (wfs-spatial-sigma)
      (detector:spatial-sigma detector-index))

    (define (wfs-temporal-sigma)
      (detector:temporal-sigma detector-index))

    (define (wfs-s-vector)
      (check-wfs-initialized)
      (detector:s-vector detector-index))

    (define (wfs-r-vector)
      (check-wfs-initialized)
      (detector:r-vector detector-index))

    (define (wfs-set-r-vector! x)
      (check-wfs-initialized)
      (detector:set-r-vector! detector-index x))

    (define (wfs-ds-mean-vector)
      (detector:ds-mean-vector detector-index))

    (define (wfs-ds-sigma-vector)
      (detector:ds-sigma-vector detector-index))

    (define (wfs-dt-sigma)
      (detector:dt-sigma detector-index))

    (define (wfs-idle!)
      (check-wfs-initialized)
      (foreign:detector-set-abort! detector-index 1)
      (wfs-command! #\i #\I)
      (foreign:detector-set-abort! detector-index 0)
      (values))

    (define (wfs-expose! integration-time exposure-time)
      (check-wfs-initialized)
      (when (negative? integration-time)
        (error "the integration time must be non-negative."))
      (when (negative? exposure-time)
        (error "the exposure time must be non-negative."))
      (when (and (zero? integration-time) (not (zero? exposure-time)))
        (error "the exposure time must be zero if the integration time is zero."))
      (wfs-idle!)
      (foreign:detector-set-integration-time! detector-index (inexact integration-time))
      (foreign:detector-set-exposure-time! detector-index (inexact exposure-time))
      (wfs-command! #\e #\E)
      (values))

    (define (wfs-dark! integration-time)
      (check-wfs-initialized)
      (when (negative? integration-time)
        (error "the integration time must be non-negative."))
      (wfs-idle!)
      (foreign:detector-set-integration-time! detector-index (inexact integration-time))
      (foreign:detector-set-exposure-time! detector-index (inexact integration-time))
      (wfs-command! #\d #\D)
      (values))

    (define (wfs-darks! integration-time)
      (check-wfs-initialized)
      (when (negative? integration-time)
        (error "the integration time must be non-negative."))
      (wfs-idle!)
      (log-info "saving configuration.")
      (let ((saved-name (or configuration-name 'full-frame-1)))
        (for-each
         (lambda (p)
           (let ((name (car p)))
             (log-info "taking dark for configuration " name ".")
             (wfs-configure! name)
             (wfs-wait)
             (wfs-dark! integration-time)
             (wfs-wait)))
         configuration-alist)
        (log-info "restoring configuration " saved-name ".")
        (wfs-configure! saved-name)
        (wfs-wait)
        (values)))

    (define (wfs-correct! integration-time exposure-time)
      (check-wfs-initialized)
      (when (negative? integration-time)
        (error "the integration time must be non-negative."))
      (when (negative? exposure-time)
        (error "the exposure time must be non-negative."))
      (when (and (zero? integration-time) (not (zero? exposure-time)))
        (error "the exposure time must be zero if the integration time is zero."))
      (wfs-idle!)
      (foreign:detector-set-integration-time! detector-index (inexact integration-time))
      (foreign:detector-set-exposure-time! detector-index (inexact exposure-time))
      (wfs-command! #\a #\A)
      (values))

    (define (wfs-find-spots!)
      (check-wfs-initialized)
      (wfs-idle!)
      (wfs-command! #\f #\I)
      (values))

    (define (wfs-refine-spots!)
      (check-wfs-initialized)
      (wfs-idle!)
      (wfs-command! #\r #\I)
      (values))

    (define (wfs-determine-i-matrix! n)
      (log-info "configuring the detector.")
      (wfs-configure! 'sub-frame-2)
      (wfs-wait)
      (determine-i-matrix! n
                           wfs-directory-path
                           #(0.3 0.3 0.3 0.3 0.3 0.3 0.3)
                           8
                           wfs-search-radius
                           wfs-set-search-radius!
                           4
                           wfs-refinement-radius
                           wfs-set-refinement-radius!
                           wfs-expose!
                           wfs-wait
                           wfs-refine-spots!
                           wfs-s-vector))

    (define (wfs-wait)
      (thread-wait-until! wfs-initialized?)
      (thread-wait-until! wfs-idle?)
      (values))

    (define (wfs-command)
      (foreign:wfs-command))

    (define (wfs-command! command acknowledgement)
      (check-wfs-initialized)
      (foreign:wfs-set-command! command)
      (thread-wait-until!
       (lambda ()
         (char=? (foreign:wfs-command) acknowledgement)))
      (values))

    (define (wfs-search-radius)
      (detector:search-radius detector-index))

    (define (wfs-set-search-radius! radius)
      (detector:set-search-radius! detector-index radius))

    (define (wfs-refinement-radius)
      (detector:refinement-radius detector-index))

    (define (wfs-set-refinement-radius! radius)
      (detector:set-refinement-radius! detector-index radius))

    (define (wfs-i-matrix)
      (detector:i-matrix detector-index))

    (define (wfs-set-i-matrix! i-matrix)
      (detector:set-i-matrix! detector-index i-matrix))

    (define (wfs-c-matrix)
      (detector:c-matrix detector-index))

    (define (wfs-set-c-matrix! c-matrix)
      (detector:set-c-matrix! detector-index c-matrix))

    (define (wfs-g-vector)
      (detector:g-vector detector-index))

    (define (wfs-set-g-vector! gain)
      (detector:set-g-vector! detector-index gain))))
